import random
import time
from datetime import datetime
from threading import Thread, Lock


class Participant:
    def __init__(self, participant_id, tools_request_list, tools_participant_list, tools_lock):
        self.request_list = tools_request_list  # 绑定共享的请求列表
        self.participant_list = tools_participant_list  # 绑定到共享的参与者列表
        self.responded_list = []  # 已响应列表，记录自己已响应过的请求，防止重复响应
        self.participant_id = participant_id  # 参与者id,主线程创建该参与者时提供
        self.request_time = datetime.now()  # 请求时间
        self.response_time = datetime.now()  # 处理响应时间
        self.get_response_num = -1  # 请求同意的个数
        self.is_using = False  # 是否正在使用临界资源，默认为False
        self.lock = tools_lock  # 绑定同一个资源互斥锁

    def start(self):
        while 1:
            if self.get_response_num == -1:  # 如果请求响应个数为-1，说明还未发起请求，则可以发起下一次请求
                self.get_response_num = 0  # 将得到响应的个数初始化为0
                self.request_time = datetime.now()
                self.send_request()
            # 是否集齐n-1个同意，因为只同意比自己早地请求，因此如果自己收集到所有同意，则自己一定排在请求队列首位
            if self.get_response_num == len(self.participant_list) - 1:
                with self.lock:  # 保证访问临界资源时为原子操作，相当于给这个操作加锁
                    self.working()
            self.agree()  # 每次循环都调用一次agree,判断是否有新请求可以被同意
            time.sleep(random.randint(1, 4))  # 随机时长的等待

    def send_request(self):
        self.request_list.append(f'{self.request_time}_{self.participant_id}')
        self.request_list.sort()  # 按照请求时间排序
        print(f'参与者{self.participant_id}:已于{self.request_time} 发送请求，正在等待响应！')

    def working(self):
        self.is_using = True  # 标记为正在使用临界资源
        print(f'参与者{self.participant_id}:请求已全体通过，开始访问临界资源！')
        # TODO：此处可以为访问临界资源的相关操作,此处用睡眠1秒代替
        time.sleep(1)
        self.get_response_num = -1  # 访问完成，请求响应数置为-1，即未请求状态
        self.is_using = False  # 标记自己为未使用状态
        print(f'参与者{self.participant_id}:访问临界资源完成，可以继续工作了！')
        # 删除自己的请求，此处使用del的目的是在删除元素的同时释放其空间，节约内存，
        # 如果仅想达到删除元素的目的，也可以简单的使用列表自带的remove方法
        self.delete_request()
        return

    def delete_request(self):
        # 删除自己的请求，此处使用del的目的是在删除元素的同时释放其空间，节约内存，
        # 如果仅想达到删除元素的目的，也可以简单的使用列表自带的remove方法
        req_index = self.request_list.index(f'{self.request_time}_{self.participant_id}')  # 拿到请求值的索引
        del self.request_list[req_index]  # 根据索引del(销毁)列表中该请求的元素对象

    def agree(self):
        # 同意请求的相关操作
        if self.is_using:  # 自己正在使用
            return
        request_list = self.request_list.copy()  # 使用浅拷贝，复制一份副本，否则会直接和这个复制对象绑定，同步更新
        response_index = len(request_list)  # 默认截断值为整个队列长度
        if f'{self.request_time}_{self.participant_id}' in request_list:  # 如果自己已经添加请求到请求列表，则截断比自己早的队列部分
            response_index = request_list.index(f'{self.request_time}_{self.participant_id}')
        response_obj_list = request_list[0:response_index]  # 只同意比自己早的请求
        for response_obj in response_obj_list:  # 不重复同意已同意过的
            if response_obj not in self.responded_list:
                response = response_obj.split('_')[1]
                response = int(response)
                self.participant_list[response].get_response_num += 1
                self.response_time = datetime.now()
                print(f'参与者{self.participant_id}：来自参与者{response}的请求处理完成！处理时间：{self.response_time}')
        self.responded_list = response_obj_list  # 更新已响应列表


class Tools:  # 工具类，只起到一个提供共享数据的途径，实际应用时可以替换为其他通信方式以共享数据
    def __init__(self):
        self.participant_list = []  # 参与者列表
        self.request_list = []  # 请求列表
        self.lock = Lock()  # 临界资源访问的锁


def create_thread(num):
    global participant_thread_list

    # 创建工具类
    tools = Tools()

    # 创建参与者线程
    for i in range(0, num):  # TODO：可扩展为线程池的方式
        participant = Participant(i, tools.request_list, tools.participant_list, tools.lock)
        tools.participant_list.append(participant)  # 添加到协调者拥有的参与者列表
        participant_i = Thread(target=participant.start, name=f'participant_{i}')
        participant_thread_list.append(participant_i)
        participant_i.setDaemon(True)  # 守护线程
        participant_i.start()  # 参与者线程启动

    for i in range(0, num):
        participant_thread_list[i].join()


def main(num):
    create_thread(num)  # 创建参与者和协调者，开始工作


if __name__ == '__main__':
    participant_thread_list = []  # 参与者线程列表
    participant_num = int(input("请输入参与者个数："))
    main(participant_num)
